package net.wanderinghorse.jsonmessage.dispatch;

import net.wanderinghorse.json.JSONUtil;
import net.wanderinghorse.jsonmessage.*;

import java.io.PrintWriter;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.*;

/**
 * Acts as a Servlet which accepts JSONMessage-style Requests via GET/POST, dispatches the request to a
 * Response handler, and outputs the response as JSON text using the JSONMessage Response data structure
 * conventions.
 */
public class RequestDispatcherServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;

    /**
     * Generates Response-formatted JSON encapsulating error details.
     * @param req The request object for which we are generating the error. May be null.
     * @param rc The response's resultCode.
     * @param errType The response's errorType.
     * @param msg The error string.
     * @return A JSON-formatted Response suitable for sending back to
     * the originating Request sender.
     */
    private String generateErrorJSON( Request req, int rc, int errType, String msg ) {
        try {
            Response resp = new ErrorResponse(rc, errType, msg);
            if( null != req ) {
                resp.setRequestId( req.getId() );
            }
            return resp.toString();
        }catch( Exception je ) {
            /* Cannot happen (except on OOM error), as ErrorResponse intentionally
               doesn't internally do anything which may cause an exception.
            */
            return "{\"type\":\"InternalError\",\"id\":0,\"timestamp\":0}";
        }
    }
//    private String generateErrorJSON( int rc, Response.ErrTypes err ) {
//        return generateErrorJSON( rc, err.getValue(), err.getMessage() );
//    }

    /**
     * May be re-implemented to return a specific handler for a given request.
     * @param request The request to find a handler for.
     * @return A RequestHandler instance appropriate to the request, or null
     * if none was found.
     */
    protected RequestHandler loadRequestHandler( final Request request ) {
        return RequestHandlerLoader.instance().loadRequestHandler( request );
    }
    /**
     * Looks for a JSONMessage-conventional Request object, in JSON format,
     * in the GET/POST field named Request.REQUEST_KEY, or posted via POST as plain
     * JSON text.
     *
     * As output it generates JSON code, written to response.getWriter():
     *
     * a) on success, the JSON form of the Response class to which the request
     * is delegated.
     *
     * b) on error, the output will be a Response-formatted error message.
     *
     * The output is formatted as a JSONMessage-conventional Response.
     *
     * To find a message handler, this function uses ResponseHandlerLoader to try
     * to load a handler based on the logical type of the incoming JSONMessage Request
     * object. It can dispatch to both ServletResponseHandler and ResponseHandler implementations.
     *
     * @param request The input request.
     * @param response The output response.
     * @throws ServletException Never?
     * @throws IOException If any underlying i/o operations throw.
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        //String pCallback = request.getParameter( Request.JSONP_KEY );
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        String json = request.getParameter( Request.REQUEST_KEY );
        if( (null == json) )
        {
            try { // Try to read raw JSON from input stream.
                json = JSONUtil.readAll( request.getInputStream() );
            }
            catch(IOException io) {
                out.println( this.generateErrorJSON(null, 1, Response.ErrTypes.NO_REQUEST_FOUND.getValue(),
                            "Key '"+Request.REQUEST_KEY+"' not found in HTTP request data!") );
                return;
            }
            if( null == json ) {
                out.println( this.generateErrorJSON(null, 1, Response.ErrTypes.NO_REQUEST_FOUND.getValue(),
                            "Input POST data is empty.") );
                return;
            }
        }

        Request jreq;
        try {
            jreq = new Request(json);
        }
        catch(Exception je) {
            out.println( this.generateErrorJSON( null, 2, Response.ErrTypes.FROMJSON_THREW.getValue(),
                "Error converting Request object from JSON: "+
                je.getMessage()) );
            return;
        }

        RequestHandler hnd;
        try {
            hnd = loadRequestHandler(jreq);
        }
        catch(Exception e) {
            out.println( this.generateErrorJSON( jreq, 3, Response.ErrTypes.NO_RESPONSE_FOUND.getValue(),
                          "Exception ("+e.getClass().getName()+") while loading response handler for "
                          +"Request type ["+jreq.getType()+"]: "+e.getMessage())
            );
            return;

        }
        if( null == hnd ) {
            out.println( this.generateErrorJSON( jreq, 3, Response.ErrTypes.NO_RESPONSE_FOUND.getValue(),
                         "No responder found/loaded for Request type ["+jreq.getType()+"].")
            );
            return;
        }
        Response jres = new Response();
        jres.setRequestId( jreq.getId() );
        try {
            if( hnd instanceof RequestHandlerServlet) {
                ((RequestHandlerServlet)hnd).processResponse(
                        new RequestHandlerServlet.ServletInfo(request, response),
                        jreq, jres);
            }
            else {
                hnd.processResponse(jreq, jres);
            }
        }
        catch(Exception je) {
            out.println( this.generateErrorJSON(jreq, 4 , Response.ErrTypes.RESPONSE_THREW.getValue(),
                    "Response handler threw: "+je.toString()  ) );
            return;
        }
        try {
            json = jres.toJSON();
        }
        catch(Exception je) {
            out.println( this.generateErrorJSON(jreq, 5 , Response.ErrTypes.TOJSON_THREW.getValue(),
                    "Response handler threw: "+je.toString()  ) );
            return;
        }
//        if(false) {
//            // ARGUABLE
//            Cookie cookies[] = request.getCookies();
//            if( null != cookies ) {
//                final String key = "JSESSIONID";
//                boolean found = false;
//                for( Cookie c : cookies ) {
//                    if( key.equals(c.getName()) ) {
//                        found = true;
//                        break;
//                    }
//                }
//                if(! found || !request.isRequestedSessionIdValid()) {
//                    final HttpSession hsess = request.getSession(true);
//                    response.addCookie(new Cookie(key,hsess.getId()));
//                }
//            }
//        }
        out.println( json );
        out.flush();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                        throws ServletException, IOException
    {
        processRequest(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
                        throws ServletException, IOException
    {
        processRequest(request, response);
    }

}
